/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <asm/io.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <asm/unaligned.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/hardirq.h>
#include <linux/kgdb.h>

#include <alga/amd/atombios/atb.h>

#include "atb.h"
#include "tables/atb.h"
#include "tables/cmd.h"
#include "tables/data.h"
#include "tables/iio.h"

#include "interpreter.h"
#include "regs.h"
#include "iio_interpreter.h"

#define OPS_CNT 123
#define OP_EOT 91

#define ARG_REG			0
#define ARG_PS			1
#define ARG_WS			2
#define ARG_FB			3
#define ARG_ID			4 /* InDirect */
#define ARG_IMM			5
#define ARG_PLL			6
#define ARG_MC			7

#define COND_ABOVE		0
#define COND_ABOVEOREQUAL	1
#define COND_ALWAYS		2
#define COND_BELOW		3
#define COND_BELOWOREQUAL	4
#define COND_EQUAL		5
#define COND_NOTEQUAL		6

#define CASE_MAGIC		0x63
#define CASE_END		0x5a5a

#define PORT_AMD		0	/* direct or indirect */
#define PORT_PCI		1
#define PORT_SYSIO		2


#define UNIT_MICROSEC		0
#define UNIT_MILLISEC		1

#define TGT_DW			0
#define TGT_WD_0		1
#define TGT_WD_8		2
#define TGT_WD_16		3
#define TGT_BYTE0		4
#define TGT_BYTE8		5
#define TGT_BYTE16		6
#define TGT_BYTE24		7

#define WS_QUOTIENT		0x40
#define WS_REMAINDER		0x41
#define WS_DATA_PTR		0x42
#define WS_SHIFT		0x43
#define WS_OR_MASK		0x44
#define WS_AND_MASK		0x45
#define WS_FB_WND		0x46
#define WS_ATTRIBS		0x47
#define WS_REG_PTR  		0x48

static u32 arg_mask[8] = {
	0xffffffff,
	0xffff,
	0xffff00,
	0xffff0000,
	0xff,
	0xff00,
	0xff0000,
	0xff000000
};
static u8 arg_shift[8] = {0, 0, 8, 16, 0, 8, 16, 24};

/*
 * map the attribute destination alignment/size mapping to be the same
 * than the source from the source alignment/size value
 */
static u8 dst_src_identity[8] = {0, 0, 1, 2, 0, 1, 2, 3};

/*
 * map destination argument alignment/size from source alignment/size
 * ([5:3] from the attribute byte) and [7:6] from the attribute byte.
 */
static u8 dst_align_map[8][4] = {
	{0, 0, 0, 0},
	{1, 2, 3, 0},
	{1, 2, 3, 0},
	{1, 2, 3, 0},
	{4, 5, 6, 7},
	{4, 5, 6, 7},
	{4, 5, 6, 7},
	{4, 5, 6, 7}
};

/* adjust the size of the parameter space and zero the new space */
static long ps_sz_zadjust(struct ictx *ictx, u32 idx)
{
	u16 required_dws;

	required_dws = ictx->ps_frame + idx + 1;

	if (required_dws > ictx->atb->g_ctx.ps_dws) {
		u32 *new_ps_top;
		u16 new_dws;

		new_ps_top = krealloc(ictx->atb->g_ctx.ps_top, required_dws,
								GFP_KERNEL);
		if (!new_ps_top)
			return -ATB_ERR;

		new_dws = required_dws - ictx->atb->g_ctx.ps_dws;
		memset(new_ps_top + ictx->ps_frame + idx + 1 - new_dws, 0,
								new_dws * 4);

		ictx->atb->g_ctx.ps_top = new_ps_top;
		ictx->atb->g_ctx.ps_dws = required_dws;
	}
	return 0;
}

/*
 * return the target value properly masked and shifted. The u32 from where
 * the target value is from, is stored in saved, if provided.
 */
static u32 get_val(struct ictx *ictx, u8 attr, u16 *ptr, u32 *saved)
{
	u32 idx;
	u32 val;
	u32 align;
	u32 arg;

	val = 0xcdcdcdcd;
	arg = attr & 7;
	align = (attr >> 3) & 7;

	switch (arg) {
	case ARG_REG:
		idx = get_unaligned_le16(ictx->atb->adev.rom + *ptr);
		*ptr += 2;

		idx += ictx->atb->g_ctx.regs_blk;

		if (ictx->atb->g_ctx.io_mode & IO_IIO) {
			if (!ictx->atb->iio[ictx->atb->g_ctx.io_mode & 0x7f]) {
				dev_err(ictx->atb->adev.dev, "atombios:undefined indirect io read program %d.\n",
					ictx->atb->g_ctx.io_mode & 0x7f);
				return 0;
			}
			val = iio_interpret(ictx,
				ictx->atb->iio[ictx->atb->g_ctx.io_mode & 0x7f], idx,
									0);
		} else {
			switch (ictx->atb->g_ctx.io_mode) {
		        case IO_MM:
				val = ictx->atb->adev.rr32(
						ictx->atb->adev.dev, 4 * idx);
				break;
			case IO_PCI:
			case IO_SYSIO:
				break;
			default:
				dev_err(ictx->atb->adev.dev, "atombios:get:bad io mode\n");
				return 0;
			}
		}
		break;
	case ARG_PS:
		idx = *(u8*)(ictx->atb->adev.rom + *ptr);
		(*ptr)++;

		/* in case it tries to access unallocated memory  */
		if (ps_sz_zadjust(ictx, idx) != 0) { 
			ictx->abort = 1;
			return 0;
		}

		val = ictx->atb->g_ctx.ps_top[ictx->ps_frame + idx];
		break;
	case ARG_WS:
		idx = *(u8*)(ictx->atb->adev.rom + *ptr);
		(*ptr)++;

		switch (idx) {
		case WS_QUOTIENT:
			val = ictx->atb->g_ctx.divmul[0];
			break;
		case WS_REMAINDER:
			val = ictx->atb->g_ctx.divmul[1];
			break;
		case WS_DATA_PTR:
			val = ictx->atb->g_ctx.data_blk;
			break;
		case WS_SHIFT:
			val = ictx->atb->g_ctx.shift;
			break;
		case WS_OR_MASK:
			val = 1 << ictx->atb->g_ctx.shift;
			break;
		case WS_AND_MASK:
			val = ~(1 << ictx->atb->g_ctx.shift);
			break;
		case WS_FB_WND:
			val = ictx->atb->g_ctx.fb_wnd;
			break;
		case WS_ATTRIBS:
			val = ictx->atb->g_ctx.io_attr;
			break;
		case WS_REG_PTR:
			val = ictx->atb->g_ctx.regs_blk;
			break;
		default:
			val = get_unaligned_le32(ictx->ws + idx);
		}
		break;
	case ARG_ID:
		idx = get_unaligned_le16(ictx->atb->adev.rom + *ptr);
		*ptr += 2;

		val = get_unaligned_le32(ictx->atb->adev.rom + idx
						+ ictx->atb->g_ctx.data_blk);
		break;
	case ARG_FB:
		idx = *(u8*)(ictx->atb->adev.rom + *ptr);
		(*ptr)++;

		//XXX:carefull, upstream does not byteswap this
		val = le32_to_cpup(ictx->atb->scratch
					+ ictx->atb->g_ctx.fb_wnd / 4 + idx);
		break;
	case ARG_IMM:/* immediate, then return raw value */
		switch (align) {
		case TGT_DW:
			val = get_unaligned_le32(ictx->atb->adev.rom + *ptr);
			*ptr += 4;
			return val;
		case TGT_WD_0:
		case TGT_WD_8:
		case TGT_WD_16:
			val = get_unaligned_le16(ictx->atb->adev.rom + *ptr);
			*ptr += 2;
			return val;
		case TGT_BYTE0:
		case TGT_BYTE8:
		case TGT_BYTE16:
		case TGT_BYTE24:
			val = *(u8*)(ictx->atb->adev.rom + *ptr);
			(*ptr)++;
			return val;
		}
		return 0;
	case ARG_PLL:
		idx = *(u8*)(ictx->atb->adev.rom + *ptr);
		(*ptr)++;
		dev_err(ictx->atb->adev.dev, "atombios:pll register access unimplemented\n");
		val = 0;
		break;
	case ARG_MC:
		idx = *(u8*)(ictx->atb->adev.rom + *ptr);
		(*ptr)++;
		dev_err(ictx->atb->adev.dev, "atombios:mc register access unimplemented\n");
		val = 0;
		break;
	}

	if (saved)
		*saved = val;
	val &= arg_mask[align];
	val >>= arg_shift[align];
	return val;
}

/*
 * return the dst value properly masked and shifted. The u32 from where
 * the dst value is from, is stored in saved, if provided.
 */
static u32 get_dst(struct ictx *ictx, u8 arg, u8 attr, u16 *ptr, u32 *saved)
{
	return get_val(ictx, arg
		| dst_align_map[(attr >> 3) & 7][(attr >> 6) & 3] << 3, ptr,
									saved);
}

static void skip_val(struct ictx *ictx, u8 attr, u16 *ptr)
{
	u32 align;
	u32 arg;

	align = (attr >> 3) & 7;
	arg = attr & 7;

	switch (arg) {
	case ARG_REG:
	case ARG_ID:
		*ptr += 2;
		break;
	case ARG_PLL:
	case ARG_MC:
	case ARG_PS:
	case ARG_WS:
	case ARG_FB:
		(*ptr)++;
		break;
	case ARG_IMM:
		switch (align) {
		case TGT_DW:
			*ptr += 4;
			break;
		case TGT_WD_0:
		case TGT_WD_8:
		case TGT_WD_16:
			*ptr += 2;
			break;
		case TGT_BYTE0:
		case TGT_BYTE8:
		case TGT_BYTE16:
		case TGT_BYTE24:
			(*ptr)++;
			break;
		}
		break;
	}
}

static void skip_dst(struct ictx *ictx, u8 arg, u8 attr, u16 *ptr)
{
	skip_val(ictx, arg | dst_align_map[(attr >> 3) & 7][(attr >> 6) & 3]
								<< 3, ptr);
}

static u32 get_src(struct ictx *ictx, u8 attr, u16 *ptr)
{
	return get_val(ictx, attr, ptr, NULL);
}

static void put_dst(struct ictx *ictx, u8 arg, u8 attr, u16 *ptr, u32 val,
								u32 saved)
{
	u32 dst_align;
	u32 old_val;
	u32 idx;

	dst_align = dst_align_map[(attr >> 3) & 7][(attr >> 6) & 3];
	
	old_val = val;
	old_val &= arg_mask[dst_align] >> arg_shift[dst_align];

	val <<= arg_shift[dst_align];
	val &= arg_mask[dst_align];

	saved &= ~arg_mask[dst_align];
	val |= saved;

	switch (arg) {
	case ARG_REG:
		idx = get_unaligned_le16(ictx->atb->adev.rom + *ptr);
		*ptr += 2;

		idx += ictx->atb->g_ctx.regs_blk;

		if (ictx->atb->g_ctx.io_mode & IO_IIO) {
			if (!ictx->atb->iio[ictx->atb->g_ctx.io_mode & 0xff]) {
				dev_err(ictx->atb->adev.dev, "atombios:undefined indirect io write program %d.\n",
					ictx->atb->g_ctx.io_mode & 0x7f);
				return;
			}
			iio_interpret(ictx,
				ictx->atb->iio[ictx->atb->g_ctx.io_mode & 0xff], idx,
									val);
		} else {
			switch (ictx->atb->g_ctx.io_mode) {
		        case IO_MM:
		        	if (idx == 0) {
					/*
					 * for indirect reg access val is
					 * actually a reg dword index.
					 */
					ictx->atb->adev.wr32(
						ictx->atb->adev.dev, 4 * val,
									0);
				} else {
					ictx->atb->adev.wr32(
						ictx->atb->adev.dev, val,
								4 * idx);
				}
				break;
			case IO_PCI:
			case IO_SYSIO:
				break;
			default:
				dev_err(ictx->atb->adev.dev, "atombios:bad io mode\n");
				return;
			}
		}
		break;
	case ARG_PS:
		idx = *(u8*)(ictx->atb->adev.rom + *ptr);
		(*ptr)++;

		if (ps_sz_zadjust(ictx, idx) != 0) {
			ictx->abort = 1;
			return;
		}

		ictx->atb->g_ctx.ps_top[ictx->ps_frame + idx] =
							cpu_to_le32(val);
		break;
	case ARG_WS:
		idx = *(u8*)(ictx->atb->adev.rom + *ptr);
		(*ptr)++;

		switch (idx) {
		case WS_QUOTIENT:
			ictx->atb->g_ctx.divmul[0] = val;
			break;
		case WS_REMAINDER:
			ictx->atb->g_ctx.divmul[1] = val;
			break;
		case WS_DATA_PTR:
			ictx->atb->g_ctx.data_blk = val;
			break;
		case WS_SHIFT:
			ictx->atb->g_ctx.shift = val;
			break;
		case WS_OR_MASK:
		case WS_AND_MASK:
			break;
		case WS_FB_WND:
			ictx->atb->g_ctx.fb_wnd = val;
			break;
		case WS_ATTRIBS:
			ictx->atb->g_ctx.io_attr = val;
			break;
		case WS_REG_PTR:
			ictx->atb->g_ctx.regs_blk = val;
			break;
		default:
			ictx->ws[idx] = cpu_to_le32(val);
		}
		break;
	case ARG_FB:
		idx = *(u8*)(ictx->atb->adev.rom + *ptr);
		(*ptr)++;

		//XXX:carefull, upstream does not byteswap this
		ictx->atb->scratch[ictx->atb->g_ctx.fb_wnd / 4 + idx] =
							cpu_to_le32(val);
		break;
	case ARG_PLL:
		idx = *(u8*)(ictx->atb->adev.rom + *ptr);
		(*ptr)++;
		dev_err(ictx->atb->adev.dev, "atombios:pll register access unimplemented\n");
		break;
	case ARG_MC:
		idx = *(u8*)(ictx->atb->adev.rom + *ptr);
		(*ptr)++;
		dev_err(ictx->atb->adev.dev, "atombios:mc register access unimplemented\n");
		break;
	}
}

static void op_move(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u32 src;
	u32 saved_dst_u32;
	u16 dst_ptr;

	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);
	dst_ptr = *ptr;

	if (((attr >> 3) & 7) != TGT_DW)
		get_dst(ictx, arg, attr, ptr, &saved_dst_u32);
	else {
		skip_dst(ictx, arg, attr, ptr);
		saved_dst_u32 = 0xcdcdcdcd;
	}
	src = get_src(ictx, attr, ptr);
	put_dst(ictx, arg, attr, &dst_ptr, src, saved_dst_u32);
}

static void op_and(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u32 dst;
	u32 src;
	u32 saved_dst_u32;
	u16 dst_ptr;

	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);
	dst_ptr = *ptr;

	dst = get_dst(ictx, arg, attr, ptr, &saved_dst_u32);
	src = get_src(ictx, attr, ptr);

	dst &= src;

	put_dst(ictx, arg, attr, &dst_ptr, dst, saved_dst_u32);
}

static void op_or(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u32 dst;
	u32 src;
	u32 saved_dst_u32;
	u16 dst_ptr;

	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);
	dst_ptr = *ptr;

	dst = get_dst(ictx, arg, attr, ptr, &saved_dst_u32);
	src = get_src(ictx, attr, ptr);

	dst |= src;

	put_dst(ictx, arg, attr, &dst_ptr, dst, saved_dst_u32);
}

static u32 get_val_direct(struct ictx *ictx, u8 align, u16 *ptr)
{
	u32 val;

	val = 0xcdcdcdcd;

	switch (align) {
	case TGT_DW:
		val = get_unaligned_le32(ictx->atb->adev.rom + *ptr);
		*ptr += 4;
		break;
	case TGT_WD_0:
	case TGT_WD_8:
	case TGT_WD_16:
		val = get_unaligned_le16(ictx->atb->adev.rom + *ptr);
		*ptr += 2;
		break;
	case TGT_BYTE0:
	case TGT_BYTE8:
	case TGT_BYTE16:
	case TGT_BYTE24:
		val = *(u8*)(ictx->atb->adev.rom + *ptr);
		(*ptr)++;
		break;
	}
	return val;
}

static void op_shift_left(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u8 shift;
	u32 dst;
	u32 saved_dst_u32;
	u16 dst_ptr;
	
	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);
	dst_ptr = *ptr;
	
	/*
	 * build attribute destination alignment/size mapping in order to
	 * get destination the same alignment/size than source
	 */
	attr &= 0x38;
	attr |= dst_src_identity[attr >> 3] << 6;

	dst = get_dst(ictx, arg, attr, ptr, &saved_dst_u32);
	shift = (u8)get_val_direct(ictx, TGT_BYTE0, ptr);

	dst <<= shift;

	put_dst(ictx, arg, attr, &dst_ptr, dst, saved_dst_u32);
}

static void op_shift_right(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u8 shift;
	u32 dst;
	u32 saved_dst_u32;
	u16 dst_ptr;

	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);
	dst_ptr = *ptr;

	/* see op_shift_left */
	attr &= 0x38;
	attr |= dst_src_identity[attr >> 3] << 6;

	dst = get_dst(ictx, arg, attr, ptr, &saved_dst_u32);
	shift = get_val_direct(ictx, TGT_BYTE0, ptr);

	dst >>= shift;

	put_dst(ictx, arg, attr, &dst_ptr, dst, saved_dst_u32);
}

static void op_mul(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u32 dst;
	u32 src;

	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);

	dst = get_dst(ictx, arg, attr, ptr, NULL);
	src = get_src(ictx, attr, ptr);

	ictx->atb->g_ctx.divmul[0] = dst * src;
}

static void op_div(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u32 dst;
	u32 src;

	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);

	dst = get_dst(ictx, arg, attr, ptr, NULL);
	src = get_src(ictx, attr, ptr);

	if (src != 0) {
		ictx->atb->g_ctx.divmul[0] = dst / src;
		ictx->atb->g_ctx.divmul[1] = dst % src;
	} else {
		ictx->atb->g_ctx.divmul[0] = 0;
		ictx->atb->g_ctx.divmul[1] = 0;
	}
}

static void op_add(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u32 dst;
	u32 src;
	u32 saved_dst_u32;
	u16 dst_ptr;

	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);
	dst_ptr = *ptr;

	dst = get_dst(ictx, arg, attr, ptr, &saved_dst_u32);
	src = get_src(ictx, attr, ptr);

	dst += src;

	put_dst(ictx, arg, attr, &dst_ptr, dst, saved_dst_u32);
}

static void op_sub(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u32 dst;
	u32 src;
	u32 saved_dst_u32;
	u16 dst_ptr;

	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);
	dst_ptr = *ptr;

	dst = get_dst(ictx, arg, attr, ptr, &saved_dst_u32);
	src = get_src(ictx, attr, ptr);

	dst -= src;

	put_dst(ictx, arg, attr, &dst_ptr, dst, saved_dst_u32);
}

static void op_set_port(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u16 port;

	switch (arg) {
	case PORT_AMD:
		port = get_unaligned_le16(ictx->atb->adev.rom + *ptr);
		if (!port)
			ictx->atb->g_ctx.io_mode = IO_MM;
		else
			ictx->atb->g_ctx.io_mode = IO_IIO | port;
		*ptr += 2;
		break;
	case PORT_PCI:
		ictx->atb->g_ctx.io_mode = IO_PCI;
		(*ptr)++;
		break;
	case PORT_SYSIO:
		ictx->atb->g_ctx.io_mode = IO_SYSIO;
		(*ptr)++;
		break;
	}
}

//XXX:checked-->exactly the same
static void op_set_regs_blk(struct ictx *ictx, u16 *ptr, u8 arg)
{
	ictx->atb->g_ctx.regs_blk = get_unaligned_le16(ictx->atb->adev.rom
									+ *ptr);
	*ptr += 2;
}

static void op_set_fb_wnd(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;

	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);
	ictx->atb->g_ctx.fb_wnd = get_src(ictx, attr, ptr);
}

static void op_compare(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u32 dst;
	u32 src;

	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);

	dst = get_dst(ictx, arg, attr, ptr, NULL);
	src = get_src(ictx, attr, ptr);

	ictx->atb->g_ctx.equal = (dst == src);
	ictx->atb->g_ctx.above = (dst > src);
}

static void op_switch(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u32 src;
	u32 val;
	u32 tgt;

	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);

	src = get_src(ictx, attr, ptr);

	while (get_unaligned_le16(ictx->atb->adev.rom + *ptr) != CASE_END)
		if (*(u8*)(ictx->atb->adev.rom + *ptr) == CASE_MAGIC) {
			(*ptr)++;
			val = get_src(ictx, (attr & 0x38) | ARG_IMM, ptr);
			tgt = get_unaligned_le16(ictx->atb->adev.rom + *ptr);
			if (val == src) {
				*ptr = ictx->tbl + tgt; /* relative jump */
				return;
			}
			*ptr += 2;
		} else {
			dev_warn(ictx->atb->adev.dev, "atombios:wrong switch case\n");
			return;
		}
	*ptr += 2;
}

static void op_jump(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 do_jump;
	u16 tgt;

	do_jump = 0;
	tgt = get_unaligned_le16(ictx->atb->adev.rom + *ptr);

	*ptr += 2;
	switch (arg) {
	case COND_ABOVE:
		do_jump = ictx->atb->g_ctx.above;
		break;
	case COND_ABOVEOREQUAL:
		do_jump = ictx->atb->g_ctx.above || ictx->atb->g_ctx.equal;
		break;
	case COND_ALWAYS:
		do_jump = 1;
		break;
	case COND_BELOW:
		do_jump = !(ictx->atb->g_ctx.above || ictx->atb->g_ctx.equal);
		break;
	case COND_BELOWOREQUAL:
		do_jump = !ictx->atb->g_ctx.above;
		break;
	case COND_EQUAL:
		do_jump = ictx->atb->g_ctx.equal;
		break;
	case COND_NOTEQUAL:
		do_jump = !ictx->atb->g_ctx.equal;
		break;
	}

	if (do_jump)
		*ptr = ictx->tbl + tgt;
}

static void op_test(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u32 dst;
	u32 src;

	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);

	dst = get_dst(ictx, arg, attr, ptr, NULL);
	src = get_src(ictx, attr, ptr);

	ictx->atb->g_ctx.equal = ((dst & src) == 0);
}

static void op_delay(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 count;

	count = *(u8*)(ictx->atb->adev.rom + (*ptr)++);

	/* using proper timer API can become tricky, see linux doc */
	if (arg == UNIT_MICROSEC)
		udelay(count);
	/* warning: in_atomic should not be used in driver */
	else if (in_atomic() || in_dbg_master() || irqs_disabled())
		mdelay(count);
	else
		msleep(count);
}

static void op_call_tbl(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 idx;
	u16 of;
	struct master_cmd_tbl *cmd_hdr;
	struct common_cmd_tbl_hdr *tbl_hdr;
	u32 ps_frame_next;
	s8 r;
	
	idx = *(u8*)(ictx->atb->adev.rom + (*ptr)++);

	of = get_unaligned_le16(&ictx->atb->hdr->master_cmd_tbl_of);
	cmd_hdr = ictx->atb->adev.rom + of;

	of = get_unaligned_le16((__le16*)(&cmd_hdr->list) + idx);
	if (!of) {
		dev_err(ictx->atb->adev.dev, "atombios:table code missing\n");
		ictx->abort = 1;
		return;
	}

	tbl_hdr = ictx->atb->adev.rom + of;
	ps_frame_next = ictx->ps_frame + ictx->ps_frame_dws;
	
	r = interpret(ictx->atb, of, ps_frame_next, idx);

	if (r)
		ictx->abort = 1;
}

static void op_repeat(struct ictx *ictx, u16 *ptr, u8 arg)
{
	dev_warn(ictx->atb->adev.dev, "atombios:op_repeat unimplemented\n");
}

static void op_clr(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u32 saved_u32_dst;
	u16 dst_ptr;

	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);
	dst_ptr = *ptr;

	attr &= 0x38;
	attr |= dst_src_identity[attr >> 3] << 6;

	get_dst(ictx, arg, attr, ptr, &saved_u32_dst);

	put_dst(ictx, arg, attr, &dst_ptr, 0, saved_u32_dst);
}

static void op_nop(struct ictx *ictx, u16 *ptr, u8 arg)
{
	/* nothing */
}

static void op_eot(struct ictx *ictx, u16 *ptr, u8 arg)
{
	/* functionally, a nop */
}

static void op_mask(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u32 dst;
	u32 mask;
	u32 src;
	u32 saved_u32_dst;
	u16 dst_ptr;

	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);
	dst_ptr = *ptr;

	dst = get_dst(ictx, arg, attr, ptr, &saved_u32_dst);
	mask = get_val_direct(ictx, ((attr >> 3) & 7), ptr);
	src = get_src(ictx, attr, ptr);

	dst &= mask;
	dst |= src;

	put_dst(ictx, arg, attr, &dst_ptr, dst, saved_u32_dst);
}

static void op_post_card(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 val;
	val = *(u8*)(ictx->atb->adev.rom + (*ptr)++);
}

static void op_beep(struct ictx *ictx, u16 *ptr, u8 arg)
{
	dev_warn(ictx->atb->adev.dev, "atombios:beep!\n");
}

static void op_save_reg(struct ictx *ictx, u16 *ptr, u8 arg)
{
	dev_warn(ictx->atb->adev.dev, "atombios:op_save_reg unimplemented\n");
}

static void op_restore_reg(struct ictx *ictx, u16 *ptr, u8 arg)
{
	dev_warn(ictx->atb->adev.dev, "atombios:op_restore_reg unimplemented\n");
}

//XXX:checked-->exactly the same
static void op_set_data_blk(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 idx;
	u16 of;
	struct master_data_tbl *data;

	idx = *(u8*)(ictx->atb->adev.rom + (*ptr)++);

	if (!idx)
		ictx->atb->g_ctx.data_blk = 0;
	else if (idx == 255)
		ictx->atb->g_ctx.data_blk = ictx->tbl; /* current cmd table?! */
	else {
		of = get_unaligned_le16(
				&ictx->atb->hdr->master_data_tbl_of);
		data = ictx->atb->adev.rom + of;	

		ictx->atb->g_ctx.data_blk = get_unaligned_le16(
						(__le16*)(&data->list) + idx);
	}
}

//XXX:not used
static void op_xor(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u32 dst;
	u32 src;
	u32 saved_u32_dst;
	u16 dst_ptr;
	
	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);
	dst_ptr = *ptr;

	dst = get_dst(ictx, arg, attr, ptr, &saved_u32_dst);
	src = get_src(ictx, attr, ptr);

	dst ^= src;

	put_dst(ictx, arg, attr, &dst_ptr, dst, saved_u32_dst);
}

//XXX:not used
static void op_shl(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u8 shift;
	u32 saved_u32_dst;
	u32 dst;
	u16 dst_ptr;
	u32 dst_align;

	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);
	dst_ptr = *ptr;

	dst_align = dst_align_map[(attr >> 3) & 7][(attr >> 6) & 3];

	dst = get_dst(ictx, arg, attr, ptr, &saved_u32_dst);

	/* op needs the whole dst u32 value */
	dst = saved_u32_dst;
	shift = get_src(ictx, attr, ptr);

	dst <<= shift;
	dst &= arg_mask[dst_align];
	dst >>= arg_shift[dst_align];

	put_dst(ictx, arg, attr, &dst_ptr, dst, saved_u32_dst);
}

//XXX:not used
static void op_shr(struct ictx *ictx, u16 *ptr, u8 arg)
{
	u8 attr;
	u8 shift;
	u32 saved_u32_dst;
	u32 dst;
	u16 dst_ptr;
	u32 dst_align;

	attr = *(u8*)(ictx->atb->adev.rom + (*ptr)++);
	dst_ptr = *ptr;
	dst_align = dst_align_map[(attr >> 3) & 7][(attr >> 6) & 3];

	dst = get_dst(ictx, arg, attr, ptr, &saved_u32_dst);

	/* op needs the whole u32 dst value */
	dst = saved_u32_dst;
	shift = get_src(ictx, attr, ptr);

	dst >>= shift;
	dst &= arg_mask[dst_align];
	dst >>= arg_shift[dst_align];

	put_dst(ictx, arg, attr, &dst_ptr, dst, saved_u32_dst);
}

static void op_debug(struct ictx *ictx, u16 *ptr, u8 arg)
{
	dev_warn(ictx->atb->adev.dev, "atombios:op_debug unimplemented\n");
}

struct op
{
	void (*func)(struct ictx *, u16 *, u8);
	u8 arg;
};

static struct op ops_tbl[OPS_CNT] = {
	{NULL, 0},
	{op_move, ARG_REG},
	{op_move, ARG_PS},
	{op_move, ARG_WS},
	{op_move, ARG_FB},
	{op_move, ARG_PLL},
	{op_move, ARG_MC},
	{op_and, ARG_REG},
	{op_and, ARG_PS},
	{op_and, ARG_WS},
	{op_and, ARG_FB},
	{op_and, ARG_PLL},
	{op_and, ARG_MC},
	{op_or, ARG_REG},
	{op_or, ARG_PS},
	{op_or, ARG_WS},
	{op_or, ARG_FB},
	{op_or, ARG_PLL},
	{op_or, ARG_MC},
	{op_shift_left, ARG_REG},
	{op_shift_left, ARG_PS},
	{op_shift_left, ARG_WS},
	{op_shift_left, ARG_FB},
	{op_shift_left, ARG_PLL},
	{op_shift_left, ARG_MC},
	{op_shift_right, ARG_REG},
	{op_shift_right, ARG_PS},
	{op_shift_right, ARG_WS},
	{op_shift_right, ARG_FB},
	{op_shift_right, ARG_PLL},
	{op_shift_right, ARG_MC},
	{op_mul, ARG_REG},
	{op_mul, ARG_PS},
	{op_mul, ARG_WS},
	{op_mul, ARG_FB},
	{op_mul, ARG_PLL},
	{op_mul, ARG_MC},
	{op_div, ARG_REG},
	{op_div, ARG_PS},
	{op_div, ARG_WS},
	{op_div, ARG_FB},
	{op_div, ARG_PLL},
	{op_div, ARG_MC},
	{op_add, ARG_REG},
	{op_add, ARG_PS},
	{op_add, ARG_WS},
	{op_add, ARG_FB},
	{op_add, ARG_PLL},
	{op_add, ARG_MC},
	{op_sub, ARG_REG},
	{op_sub, ARG_PS},
	{op_sub, ARG_WS},
	{op_sub, ARG_FB},
	{op_sub, ARG_PLL},
	{op_sub, ARG_MC},
	{op_set_port, PORT_AMD},
	{op_set_port, PORT_PCI},
	{op_set_port, PORT_SYSIO},
	{op_set_regs_blk, 0},
	{op_set_fb_wnd, 0},
	{op_compare, ARG_REG},
	{op_compare, ARG_PS},
	{op_compare, ARG_WS},
	{op_compare, ARG_FB},
	{op_compare, ARG_PLL},
	{op_compare, ARG_MC},
	{op_switch, 0},
	{op_jump, COND_ALWAYS},
	{op_jump, COND_EQUAL},
	{op_jump, COND_BELOW},
	{op_jump, COND_ABOVE},
	{op_jump, COND_BELOWOREQUAL},
	{op_jump, COND_ABOVEOREQUAL},
	{op_jump, COND_NOTEQUAL},
	{op_test, ARG_REG},
	{op_test, ARG_PS},
	{op_test, ARG_WS},
	{op_test, ARG_FB},
	{op_test, ARG_PLL},
	{op_test, ARG_MC},
	{op_delay, UNIT_MILLISEC},
	{op_delay, UNIT_MICROSEC},
	{op_call_tbl, 0},
	{op_repeat, 0},
	{op_clr, ARG_REG},
	{op_clr, ARG_PS},
	{op_clr, ARG_WS},
	{op_clr, ARG_FB},
	{op_clr, ARG_PLL},
	{op_clr, ARG_MC},
	{op_nop, 0},
	{op_eot, 0},
	{op_mask, ARG_REG},
	{op_mask,ARG_PS},
	{op_mask, ARG_WS},
	{op_mask, ARG_FB},
	{op_mask, ARG_PLL},
	{op_mask, ARG_MC},
	{op_post_card, 0},
	{op_beep, 0},
	{op_save_reg, 0},
	{op_restore_reg, 0},
	{op_set_data_blk, 0},
	{op_xor, ARG_REG},
	{op_xor, ARG_PS},
	{op_xor, ARG_WS},
	{op_xor, ARG_FB},
	{op_xor, ARG_PLL},
	{op_xor, ARG_MC},
	{op_shl, ARG_REG},
	{op_shl, ARG_PS},
	{op_shl, ARG_WS},
	{op_shl, ARG_FB},
	{op_shl, ARG_PLL},
	{op_shl, ARG_MC},
	{op_shr, ARG_REG},
	{op_shr, ARG_PS},
	{op_shr, ARG_WS},
	{op_shr, ARG_FB},
	{op_shr, ARG_PLL},
	{op_shr, ARG_MC},
	{op_debug, 0}
};

long interpret(struct atombios *atb, u16 tbl, u32 ps_frame, u8 idx)
{
	long r;
	struct ictx ictx;
	struct common_cmd_tbl_hdr *hdr;
	u16 ptr;
	s8 op;

	memset(&ictx, 0, sizeof(ictx));
	ictx.atb = atb;
	ictx.tbl = tbl;
	ictx.ps_frame = ps_frame;
	ictx.abort = 0;

	hdr = atb->adev.rom + tbl;

	ictx.ps_frame_dws = (hdr->ps_frame_sz & ATTRIB_PS_SZ_MASK) / 4;

	if (hdr->ws_dws) {
		ictx.ws = kzalloc(4 * hdr->ws_dws, GFP_KERNEL);
		if (!ictx.ws) {
			dev_err(atb->adev.dev,
				"atombios:unable to allocate workspace\n");
			return -ATB_ERR;
		}
	} else
		ictx.ws = NULL;

	ptr = tbl + sizeof(*hdr);
	r = 0;
	while (1) {
		op = *(s8*)(atb->adev.rom + ptr++);

		if (0 < op && op < OPS_CNT)
			ops_tbl[op].func(&ictx, &ptr, ops_tbl[op].arg);
		else
			break;

		if (ictx.abort) {
			dev_err(atb->adev.dev,
				"atombios:stuck executing @0x%04x\n", ptr);
			r = -ATB_ERR;
			break;
		}

		if (op == OP_EOT)
			break;
	}

	if (ictx.ws)
		kfree(ictx.ws);
	return r;
}
